<!--
@license
Copyright 2016 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../paper-slider/paper-slider.html">
<link rel="import" href="../tf-graph-common/tf-graph-common.html">
<link rel="import" href="tf-node-info.html">

<dom-module id="tf-graph-info">
<template>
<style>
:host {
  font-size: 12px;
  margin: 0;
  padding: 0;
  display: block;
}

h2 {
  padding: 0;
  text-align: center;
  margin: 0;
}

.health-pill-legend {
  padding: 15px;
}

.health-pill-legend h2 {
  text-align: left;
}

.health-pill-entry {
  margin: 10px 10px 10px 0;
}

.health-pill-entry .color-preview {
  width: 26px;
  height: 26px;
  border-radius: 3px;
  display: inline-block;
  margin: 0 10px 0 0;
}

.health-pill-entry .color-label, .health-pill-entry .tensor-count {
  color: #777;
  display: inline-block;
  height: 26px;
  font-size: 22px;
  line-height: 26px;
  vertical-align: top;
}

.health-pill-entry .tensor-count {
  float: right;
}

#health-pill-step-slider {
  width: 100%;
  margin: 0 0 0 -15px;
  /* 31 comes from adding a padding of 15px from both sides of the paper-slider, subtracting
   * 1px so that the slider width aligns with the image (the last slider marker takes up 1px),
   * and adding 2px to account for a border of 1px on both sides of the image. 30 - 1 + 2.
   * Apparently, the paper-slider lacks a mixin for those padding values. */
  width: calc(100% + 31px);
}
</style>
<template is="dom-if" if="{{selectedNode}}">
  <paper-material elevation="1" class="card">
    <tf-node-info graph-hierarchy="[[graphHierarchy]]"
                  render-hierarchy="[[renderHierarchy]]"
                  flat-graph="[[graph]]"
                  node-name="[[selectedNode]]"
                  node-include="[[selectedNodeInclude]]"
                  highlighted-node="{{highlightedNode}}"
                  color-by="[[colorBy]]">
    </tf-node-info>
  </paper-material>
</template>
<template is="dom-if" if="[[_healthPillsAvailable(nodeNamesToHealthPills)]]">
  <paper-material elevation="1" class="card health-pill-legend">
    <template is="dom-if" if="[[_maxStepIndex]]">
      <h2>
        Step of Health Pills: [[_currentStepDisplayValue]]
      </h2>
      <paper-slider
            id="health-pill-step-slider"
            immediate-value="{{healthPillStepIndex}}"
            max="[[_maxStepIndex]]"
            snaps
            step="1"
            value="{{healthPillStepIndex}}"></paper-slider>
    </template>
    <h2>
      Health Pill
      <template is="dom-if" if="[[healthPillValuesForSelectedNode]]">
        Counts for Selected Node
      </template>
      <template is="dom-if" if="[[!healthPillValuesForSelectedNode]]">
        Legend
      </template>
    </h2>
    <template is="dom-repeat" items="[[healthPillEntries]]">
      <div class="health-pill-entry">
        <div class="color-preview" style="background:[[item.background_color]]"></div>
        <div class="color-label">[[item.label]]</div>
        <div class="tensor-count">
          [[_computeTensorCountString(healthPillValuesForSelectedNode, index)]]
        </div>
      </div>
    </template>
  </paper-material>
</template>
</template>
<script>
(function() {
  Polymer({
    is: 'tf-graph-info',

    properties: {
      title: String,
      graphHierarchy: Object,
      graph: Object,
      renderHierarchy: Object,
      nodeNamesToHealthPills: Object,
      healthPillStepIndex: {
        type: Number,
        notify: true,
      },
      colorBy: String,
      // Two-ways
      selectedNode: {
        type: String,
        notify: true
      },
      highlightedNode: {
        type: String,
        notify: true
      },
      // The enum value of the include property of the selected node.
      selectedNodeInclude: {
        type: Number,
        notify: true
      },
      healthPillEntries: {
        type: Array,
        value: tf.graph.scene.healthPillEntries,
        readOnly: true,
      },
      healthPillValuesForSelectedNode: {
        type: Array,
        computed: '_computeHealthPillForNode(nodeNamesToHealthPills, healthPillStepIndex, selectedNode)',
      },
      _maxStepIndex: {
        type: Number,
        computed: '_computeMaxStepIndex(nodeNamesToHealthPills)',
      },
      _currentStepDisplayValue: {
        type: String,
        computed: '_computeCurrentStepDisplayValue(nodeNamesToHealthPills, healthPillStepIndex)',
      },
    },
    listeners: {
      'node-list-item-click': '_nodeListItemClicked',
      'node-list-item-mouseover': '_nodeListItemMouseover',
      'node-list-item-mouseout': '_nodeListItemMouseout'
    },
    _nodeListItemClicked: function(event) {
      this.selectedNode = event.detail.nodeName;
    },
    _nodeListItemMouseover: function(event) {
      this.highlightedNode = event.detail.nodeName;
    },
    _nodeListItemMouseout: function() {
      this.highlightedNode = null;
    },
    _healthPillsAvailable: function(nodeNamesToHealthPills) {
      let count = 0;
      for (let nodeName in nodeNamesToHealthPills) {
        return true;
      }
      return false;
    },
    _computeTensorCountString: function(healthPillValuesForSelectedNode, valueIndex) {
      if (!healthPillValuesForSelectedNode) {
        // No health pill data is available.
        return '';
      }

      return healthPillValuesForSelectedNode[valueIndex].toFixed(0);
    },
    _computeHealthPillForNode: function(
        nodeNamesToHealthPills, healthPillStepIndex, selectedNode) {
      if (!selectedNode) {
        // No node is selected.
        return null;
      }

      const healthPills = nodeNamesToHealthPills[selectedNode];
      if (!healthPills) {
        // This node lacks a health pill.
        return null;
      }

      const healthPill = healthPills[healthPillStepIndex];
      if (!healthPill) {
        // This node lacks a health pill at the current step.
        return null;
      }

      // The health pill count values start at 2. Each health pill contains 6 values.
      return healthPill.value.slice(2, 8);
    },
    _computeCurrentStepDisplayValue: function(nodeNamesToHealthPills, healthPillStepIndex) {
      for (let nodeName in nodeNamesToHealthPills) {
        // All nodes have the same number of steps stored, so only examine 1 node. We cannot
        // directly index into the nodeNamesToHealthPills object because we do not have a key.
        return nodeNamesToHealthPills[nodeName][healthPillStepIndex].step.toFixed(0);
      }

      // The current step could not be computed.
      return 0;
    },
    _computeMaxStepIndex: function(nodeNamesToHealthPills) {
      for (let nodeName in nodeNamesToHealthPills) {
        // All nodes have the same number of steps stored, so only examine 1 node.
        // The index is 1 less than the count. Tensorboard backend logic guarantees that the length
        // of the array will be greater than 1.
        return nodeNamesToHealthPills[nodeName].length - 1;
      }

      // Return a falsy value. The slider should be hidden.
      return 0;
    },
  });
})();
</script>
</dom-module>
